package com.cuhk.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.cuhk.common.constant.RedisKeyConstant;
import com.cuhk.gulimall.product.dao.CategoryBrandRelationDao;
import com.cuhk.gulimall.product.entity.CategoryBrandRelationEntity;
import com.cuhk.gulimall.product.utils.redis.RedisOperations;
import com.cuhk.gulimall.product.vo.CategoryLevel2Vo;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cuhk.common.utils.PageUtils;
import com.cuhk.common.utils.Query;

import com.cuhk.gulimall.product.dao.CategoryDao;
import com.cuhk.gulimall.product.entity.CategoryEntity;
import com.cuhk.gulimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    public static final int DEFAULT_SORT = 0;

    @Autowired
    private CategoryBrandRelationDao categoryBrandRelationDao;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redisson;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
        public List<CategoryEntity> listWithTree() {
        // 查询所有分类数据
        // 查询条件为null，表示全都查
        List<CategoryEntity> allEntites = baseMapper.selectList(null);

        // 获取一级类别
        List<CategoryEntity> level1Menu = allEntites.stream().filter(categoryEntity ->
            categoryEntity.getParentCid() == 0
        ).map((item) -> {
            item.setChildren(getChildrens(item, allEntites));
            return item;
        }).sorted((item1, item2) -> {
            // 防止因为sort字段没有值而造成NullPointerException
            return (item1.getSort() == null ? DEFAULT_SORT : item1.getSort()) - (item2.getSort() == null ? DEFAULT_SORT : item2.getSort());
        }).collect(Collectors.toList());

        // 将查询结果组装成树形结构
        return level1Menu;
    }

    /**
     * 递归查找menu中每一项的子菜单
     * @param categoryEntity
     * @param allEntites
     * @return
     */
    private List<CategoryEntity> getChildrens (CategoryEntity categoryEntity, List<CategoryEntity> allEntites) {
        List<CategoryEntity> childrens = allEntites.stream().filter(item -> {
            return item.getParentCid().equals(categoryEntity.getCatId());
        }).map((item) -> {
            item.setChildren(getChildrens(item, allEntites));
            return item;
        }).sorted((item1, item2) -> {
            // 防止因为sort字段没有值而造成NullPointerException
            return (item1.getSort() == null ? DEFAULT_SORT : item1.getSort()) - (item2.getSort() == null ? DEFAULT_SORT : item2.getSort());
        }).collect(Collectors.toList());
        return childrens;
    }

    /**
     * 批量删除树形结构中的分类信息
     * @param catIds
     */
    @Override
    public void removeMenusByIds(Long[] catIds) {
        // List<Long> list = new ArrayList<>();
        //TODO 检查id所对应节点没有被其他子节点引用
        // for (Long id : catId){}
        // 进行逻辑删除
        baseMapper.deleteBatchIds(Arrays.asList(catIds));
    }

    @Override
    public Long[] findCateLogPath(Long cateLogId) {
        List<Long> paths = new ArrayList<>();
        Long id = cateLogId;
        while(id != null && id != 0) {
            paths.add(id);
            CategoryEntity e = this.getById(id);
            if(e != null) {
                id = e.getParentCid();
            }
            else {
                id = null;
            }
        }
        Collections.reverse(paths);
        return paths.toArray(new Long[paths.size()]);
    }

    /**
     * 使用@CacheEvict注解配置getLevel1Categories方法生成的缓存失效
     * 因为我们采取的缓存一致性管理模式是'失效模式'
     * @param categoryEntity
     */
    // @CacheEvict(value = "category", key = "getLevel1Categories")
    //@Caching(evict = {
    //        @CacheEvict(value = "category", key = "getLevel1Categories"),
    //        @CacheEvict(value = "category", key = "getCatalogJson")
    // })

    // 批量删除category缓存命名空间下的所有缓存
    @CacheEvict(value = "category", allEntries = true)
    @Transactional
    @Override
    public void updateCascade(CategoryEntity categoryEntity) {
        this.updateById(categoryEntity);
        if(!StringUtils.isEmpty(categoryEntity.getName())) {
            CategoryBrandRelationEntity categoryBrandRelation = new CategoryBrandRelationEntity();
            categoryBrandRelation.setCatelogId(categoryEntity.getCatId());
            categoryBrandRelation.setCatelogName(categoryEntity.getName());
            UpdateWrapper<CategoryBrandRelationEntity> wrapper = new UpdateWrapper<>();
            wrapper.eq("catelog_id", categoryEntity.getCatId());
            categoryBrandRelationDao.update(categoryBrandRelation, wrapper);
        }
    }

    // 注解开启缓存
    // 触发将数据保存到缓存的操作
    @Cacheable(value = {"category"}, key = "#root.method.name", sync = true)
    @Override
    public List<CategoryEntity> getLevel1Categories() {
        System.out.println("查询了数据库-一级分类");
        QueryWrapper<CategoryEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("cat_level", 1);
        return this.list(wrapper);
    }

    @Override
    public List<CategoryEntity> getLevel2Categories(Long firstLevelCateLogId) {
        QueryWrapper<CategoryEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("cat_level", 2).eq("parent_cid", firstLevelCateLogId);
        return this.list(wrapper);
    }

    private List<CategoryEntity> getLevel3Categories(Long secondLevelCateLogId) {
        QueryWrapper<CategoryEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("cat_level", 3).eq("parent_cid", secondLevelCateLogId);
        return this.list(wrapper);
    }

    private List<CategoryEntity> getLevel2CategoriesAll() {
        QueryWrapper<CategoryEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("cat_level", 2);
        return this.list(wrapper);
    }

    private List<CategoryEntity> getLevel3CategoriesAll() {
        QueryWrapper<CategoryEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("cat_level", 3);
        return this.list(wrapper);
    }



    /**
     * 优化一下三级分类查询方法
     * 减少数据库查询次数
     * @return
     */
    @Override
    public Map<String, List<CategoryLevel2Vo>> getCatalogJsonFromDB() {
        // 所有一级类别
        // List<CategoryEntity> level1Categories = this.getLevel1Categories();
        // 所有二级类别
        List<CategoryEntity> level2Categories = this.getLevel2CategoriesAll();
        // 所有三级类别
        List<CategoryEntity> level3Categories = this.getLevel3CategoriesAll();
        // 匹配一级和二级分类的关系
        Map<String, List<CategoryLevel2Vo>> level1MatchLevel2 = new HashMap<>();
        for(CategoryEntity categoryEntity : level2Categories) {
            Long parentCid = categoryEntity.getParentCid();
            if(!level1MatchLevel2.containsKey(parentCid.toString())) {
                level1MatchLevel2.put(parentCid.toString(), new ArrayList<>());
            }
            // level1MatchLevel2.get(parentCid.toString()).add(categoryEntity);
            CategoryLevel2Vo vo = new CategoryLevel2Vo(parentCid.toString(), null, categoryEntity.getCatId().toString(), categoryEntity.getName());
            level1MatchLevel2.get(parentCid.toString()).add(vo);
        }
        Map<String, List<CategoryLevel2Vo.CategoryLevel3Vo>> level2MatchLevel3 = new HashMap<>();
        for(CategoryEntity categoryEntity : level3Categories) {
            Long parentCid = categoryEntity.getParentCid();
            if(!level2MatchLevel3.containsKey(parentCid.toString())) {
                level2MatchLevel3.put(parentCid.toString(), new ArrayList<>());
            }
            CategoryLevel2Vo.CategoryLevel3Vo vo = new CategoryLevel2Vo.CategoryLevel3Vo(parentCid.toString(), categoryEntity.getCatId().toString(), categoryEntity.getName());
            level2MatchLevel3.get(parentCid.toString()).add(vo);
        }
        for(Map.Entry<String, List<CategoryLevel2Vo>> entry : level1MatchLevel2.entrySet()) {
            List<CategoryLevel2Vo> list = entry.getValue();
            for(CategoryLevel2Vo vo : list) {
                if(level2MatchLevel3.containsKey(vo.getId())) {
                    vo.setCatalog3List(level2MatchLevel3.get(vo.getId()));
                }
            }
        }
        return level1MatchLevel2;
    }


    /**
     * 从redis缓存中获取三级分类数据
     * TODO 防止堆外内存溢出
     * @return
     */
    @Override
    public Map<String, List<CategoryLevel2Vo>> getCatalogJsonFromRedis() {

        /**
         * TODO: 加入防止缓存穿透、雪崩和击穿的代码逻辑
         */

        // 1. 查询redis缓存
        String catalogJson = (String) redisTemplate.opsForValue().get(RedisKeyConstant.CATALOG_JSON);
        if(StringUtils.isEmpty(catalogJson)) {
            // 2. redis缓存中没有数据, 在数据库中查询
            // 3. 数据库查询结果, 存储到redis中
            // redis中value的值都是String类型的JSON对象
            // 序列化
            // 避免缓存击穿 -> 加同步代码块
            RLock lock = redisson.getLock(RedisKeyConstant.CATALOG_JSON_LOCK);
            lock.lock();
            // 任何阻塞的线程进入同步代码块后
            // 还是要先查询一次缓存，如果前序进入的线程已经将DB中的查询结果刷新到了缓存
            // 则后续进入的线程就不需要在走DB了
            try {
                Map<String, List<CategoryLevel2Vo>> catalogJsonFromDB;
                String secondCheck = (String) redisTemplate.opsForValue().get(RedisKeyConstant.CATALOG_JSON);
                if(StringUtils.isEmpty(secondCheck)) {
                    // 缓存仍然不命中
                    catalogJsonFromDB = getCatalogJsonFromDB();
                    redisTemplate.opsForValue().set(RedisKeyConstant.CATALOG_JSON, JSON.toJSONString(catalogJsonFromDB));
                    return catalogJsonFromDB;
                }
                else {
                    // 缓存命中了
                    catalogJsonFromDB = JSON.parseObject(secondCheck, new TypeReference<Map<String, List<CategoryLevel2Vo>>>() {});
                    return catalogJsonFromDB;
                }
            } finally {
                lock.unlock();
            }
        }
        else {
            // 4. 缓存中有数据，直接返回
            // 返序列化
            Map<String, List<CategoryLevel2Vo>> catalogJsonFromRedis = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<CategoryLevel2Vo>>>() {});
            return catalogJsonFromRedis;
        }
    }

    /**
     * 使用注解配置缓存
     * @return
     */
    @Cacheable(value = "category", key = "#root.methodName")
    @Override
    public Map<String, List<CategoryLevel2Vo>> getCatalogJson() {
        System.out.println("查询了数据库-三级分类");
        List<CategoryEntity> level2Categories = this.getLevel2CategoriesAll();
        List<CategoryEntity> level3Categories = this.getLevel3CategoriesAll();
        Map<String, List<CategoryLevel2Vo>> level1MatchLevel2 = new HashMap<>();
        for(CategoryEntity categoryEntity : level2Categories) {
            Long parentCid = categoryEntity.getParentCid();
            if(!level1MatchLevel2.containsKey(parentCid.toString())) {
                level1MatchLevel2.put(parentCid.toString(), new ArrayList<>());
            }
            // level1MatchLevel2.get(parentCid.toString()).add(categoryEn   tity);
            CategoryLevel2Vo vo = new CategoryLevel2Vo(parentCid.toString(), null, categoryEntity.getCatId().toString(), categoryEntity.getName());
            level1MatchLevel2.get(parentCid.toString()).add(vo);
        }
        Map<String, List<CategoryLevel2Vo.CategoryLevel3Vo>> level2MatchLevel3 = new HashMap<>();
        for(CategoryEntity categoryEntity : level3Categories) {
            Long parentCid = categoryEntity.getParentCid();
            if(!level2MatchLevel3.containsKey(parentCid.toString())) {
                level2MatchLevel3.put(parentCid.toString(), new ArrayList<>());
            }
            CategoryLevel2Vo.CategoryLevel3Vo vo = new CategoryLevel2Vo.CategoryLevel3Vo(parentCid.toString(), categoryEntity.getCatId().toString(), categoryEntity.getName());
            level2MatchLevel3.get(parentCid.toString()).add(vo);
        }
        for(Map.Entry<String, List<CategoryLevel2Vo>> entry : level1MatchLevel2.entrySet()) {
            List<CategoryLevel2Vo> list = entry.getValue();
            for(CategoryLevel2Vo vo : list) {
                if(level2MatchLevel3.containsKey(vo.getId())) {
                    vo.setCatalog3List(level2MatchLevel3.get(vo.getId()));
                }
            }
        }
        return level1MatchLevel2;
    }

}